##------------------------------------------------------------------------------
##
##	yasco-1.0 - Yet Another (Java)Script Obfuscator
##	Copyright (C) 2008-2011 Oliver Schieche <post@oliver-schieche.de>
##
##	Makes use of Yapp
##	Parse::Yapp - Copyright (c) 1998-2001, Francois Desarmenien, all right reserved.
##
##	$Id: Main.pm 12 2012-10-01 13:57:30Z goresplatter1844@gmail.com $
##------------------------------------------------------------------------------
package Yasco::Main;

use strict;
use Data::Dumper;
use Error qw/:try/;
use Carp qw/confess/;
use Time::HiRes qw/time/;

use Yasco::Filter::Result;

use Yasco::Lexer;
use Yasco::Parser;

#	This is the state number, when an expression is shifted
#	and a semicolon is expected as the next token
#
#	When modifying grammar, this number might have to be
#	adjusted. Look for the following in `parser.output`:
#
#	State 35:
#
#		statement -> stmt_line . ';'	(Rule 7)
#
#		';'	shift, and go to state 77
#
use constant MISSING_SEMI_STATE		=> 35;
##------------------------------------------------------------------------------
BEGIN {
	no strict 'refs';
	foreach my $k (qw/debug p first_node/) {
		*{__PACKAGE__ . '::' . $k} = sub {
			my $self = shift;
			my $v = $self->{$k};
			$self->{$k} = shift if @_;
			$v
		}
	}

	$Yasco::Main::instance = undef;
	@Yasco::prefix_code = ();
	$Yasco::prefixed_codes = {};
	@Yasco::Node::dot_stack = ();
}
##------------------------------------------------------------------------------
sub new
	{
		my $class = shift;
		my $self = {
			options	=> {verbose => 0},
			verbose_indent => {},
			filters => {},
			filter_instance => {},
			_OB_DATA => {
				map_string => 1,
				scopes => [],
				insn_stack => [],
				getset_keyword => [0]
			}
		};

		$self->{p} = Yasco::Parser->new;
		$self->{p}->YYData->{HASHES} = [];
		$self->{p}->YYData->{LISTS} = [];
		$self->{p}->{CURSOR} = 0;
		$self->{p}->{LINE} = 0;
		$self->{p}->{FILE} = '';
		$self->{p}->{LAST_TOKEN} = undef;
		$self->{p}->{TOKEN_STACK} = [];
		$self->{p}->{IMPLICIT_INSERTS} = 0;

		$Yasco::Main::instance = $self->{p}->{OBFUSCATOR} = bless $self, $class
	}
##------------------------------------------------------------------------------
sub unshift_stack
	{
		my $self = shift;
		unshift @{$self->{_OB_DATA}->{insn_stack}}, @_;
		1
	}
##------------------------------------------------------------------------------
sub shift_stack
	{
		my $self = shift;
		shift @{$self->{_OB_DATA}->{insn_stack}};
		1
	}
##------------------------------------------------------------------------------
sub insn_stack
	{
		my $self = shift;

		return ${$self->{_OB_DATA}->{insn_stack}}[$_[0]] if @_;
		return $self->{_OB_DATA}->{insn_stack} unless wantarray;
		@{$self->{_OB_DATA}->{insn_stack}}
	}
##------------------------------------------------------------------------------
sub scope_mode
	{
		my $self = shift;
		my $mode = shift;
		my $which = shift || 0;

		${$self->{_OB_DATA}->{scopes}}[$which]->{mode} = $mode;
		1
	}
##------------------------------------------------------------------------------
sub adopt_options
	{
		my $self = shift;
		my %options = @_;

		$self->{options} = \%options;
		$self->debug(hex($options{debug} || '0x0'));

		1
	}
##------------------------------------------------------------------------------
sub add_filter
	{
		my $self = shift;
		my $filter = shift;

		eval "require $filter;";
		die $@ if $@;

		$filter = $self->{filter_instance}->{$filter} = $filter->new($self->filter_option($filter));

		(ref $self->{filters}->{$_} or $self->{filters}->{$_} = []),
		push(@{$self->{filters}->{$_}}, $filter)
			for split ',', $filter->tokens;

		1
	}
##------------------------------------------------------------------------------
sub filter_option
	{
		my $self = shift;
		my $filter = shift;
		my $result = {};
		my $opt = ($self->option(qq{filter_options}) || {})->{$filter};
		return {} unless defined($opt);

		foreach my $kv (split ',', $opt)
			{
				my ($k,$v) = split ':', $kv, 2;
				$result->{$k} = $v, next if $v !~ m{;};
				$result->{$k} = [split ';', $v];
			}

		$result

	}
##------------------------------------------------------------------------------
sub exec_filters
	{
		my $self = shift;
		my $node = shift;

		return try {
			$_->execute($self,$node)
				for @{$self->{filters}->{$node->op} || []};
			undef
		} catch Yasco::Filter::Result with {
			my $res = shift;
			$res->{-text}
		} catch Error::Simple with {
			my $exc = shift;
			confess sprintf("In %s, line %d: %s", $exc->{-file}, $exc->{-line}, $exc->{-text});
		};
	}
##------------------------------------------------------------------------------
sub get_token
	{
		my $self = shift;
		my $text = shift;
		my %pp;

		return try {
			foreach my $token (sort keys %{$self->{filters}})
				{
					foreach my $filter (@{$self->{filters}->{$token}})
						{
							my $key = ref $filter;
							next if defined($pp{$key});
							$pp{$key} = 1;
							$filter->get_token($self,$text);
						}
				}
		} catch Yasco::Filter::Result with {
			my $res = shift;
			$res->{-text}
		} catch Error::Simple with {
			my $exc = shift;
			die sprintf("In %s, line %d: %s", $exc->{-file}, $exc->{-line}, $exc->{-text});
		};
	}
##------------------------------------------------------------------------------
sub register_node
	{
		my $self = shift;
		my $node = shift;
		my %pp;

		foreach my $token (sort keys %{$self->{filters}})
			{
				foreach my $filter (@{$self->{filters}->{$token}})
					{
						my $key = ref $filter;
						next if defined($pp{$key});
						$pp{$key} = 1;
						$filter->register_node($self,$node);
					}
			}
	}
##------------------------------------------------------------------------------
sub map_string
	{
		my $self = shift;
		my $string = shift;
		my $node = Yasco::Node->new(qq{STRING}, $string);

		$node->reconstruct
	}
##------------------------------------------------------------------------------
sub obfuscate
	{
		my $self = shift;
		my $code = shift;
		my $start;

		$self->verbose(0x40, "Starting with options:\n");
		foreach my $o (sort keys %{$self->{options}}) {
			next unless $self->{options}->{$o};
			DUMP_OPT: for (ref $self->{options}->{$o}) {
				'HASH' eq $_ and do {
					$self->verbose(0x40, "\t%25s : <HASH>\n", $o);
					foreach (sort keys %{$self->{options}->{$o}}) {
						$self->verbose(0x40, "\t%25s   %25s : %s\n", '', $_, $self->{options}->{$o}->{$_});
					}
					last DUMP_OPT;
				};

				$self->verbose(0x40, "\t%25s : %s\n", $o, $self->{options}->{$o})
			}
		}

		$self->p->{DATA} = $code || '';
		$self->p->{_err_arg} = [];
		$self->verbose(0x40, "Parsing... ");

		$self->enter_scope(undef, 'find');
		$start = time;
		$self->first_node($self->p->YYParse(
			'yydebug' => ($self->debug & 0x1F),
			'yylex' => \&Yasco::Lexer::lex,
			'yyerror' => sub {
				my $p = shift;

				if (MISSING_SEMI_STATE == $p->{STACK}->[-1]->[0])
					{
						SKIP_ASI: {
							DO_ASI: {
								last SKIP_ASI if $self->option(qq{no_asi});
								last DO_ASI if $p->{LAST_TOKEN}->[1]->[0] =~ m{^(?:\r?\n)+};
								last DO_ASI if 'EOF' eq ${$p->{TOKEN}};
								last DO_ASI if '}' eq ${$p->{TOKEN}};

								printf STDERR "NOASI for: \"%s\"\n", ${$p->{TOKEN}};
								last SKIP_ASI
							}

							$p->YYErrok;
							push @{$p->{TOKEN_STACK}}, [${$p->{TOKEN}},${$p->{TOKEN}},${$p->{VALUE}}];
							${$p->{VALUE}} = undef;
							${$p->{TOKEN}} = ';';

							warn sprintf("[WARNING] Line %d: missing semicolon inserted near \"%s\"\n", 1+$p->{LINE}, $p->{LAST_TOKEN}->[0]->[0])
								unless $self->option('no_warnings');
							return;
						}
					}

				die sprintf("error in line %d, near '%s', expected '%s'", $p->{LINE}+1, ${$p->{TOKEN}}, $p->YYExpect) unless @{$p->{_err_arg}};
				@_ = @{$p->{_err_arg}};
				die sprintf("error in line %d, near '%s': %s", $p->{LINE}+1, ${$p->{TOKEN}} || '<none>', sprintf($_[0], @_[1..$#_]));
			}
		));
		$start = time - $start;
		$self->verbose(0x40, "\nParsing took %.4f seconds; processing %d nodes now...\n\t", $start, $Yasco::Node::id);

		$start = time;
		$code = $self->_obfuscate;
		$start = time - $start;

		$self->verbose(0x40, "\nFinal ratio: %.2f%%\nObfuscating took %.4f seconds.\n", 100*length($code)/length($self->p->{DATA}), $start)
			if length($self->p->{DATA});

		$code
	}
##------------------------------------------------------------------------------
sub option
	{
		my $self = shift;
		my $option = shift;
		$self->{options}->{$option}
	}
##------------------------------------------------------------------------------
sub push_insn
	{
		my $self = shift;
		my $insn = shift;

		unshift @{$self->{_OB_DATA}->{insn_stack}}, $insn;
		1
	}
##------------------------------------------------------------------------------
sub pop_insn
	{
		my $self = shift;

		shift @{$self->{_OB_DATA}->{insn_stack}};
		1
	}
##------------------------------------------------------------------------------
sub verbose
	{
		my $self = shift;
		my ($fmt,@va) = @_;

		return if $self->option('quiet');
		if ($fmt =~ m/^\d+$/)
			{
				return unless $self->debug & int($fmt);
				$fmt = ("\t"x(1+($self->{verbose_indent}->{int($fmt)} || 0))) . shift @va;
			}

		printf STDERR $fmt, @va;
		1
	}
##------------------------------------------------------------------------------
sub verbose_indent
	{
		my $self = shift;
		my ($mode,$level) = @_;
		$self->{verbose_indent}->{$mode} += $level;
	}
##------------------------------------------------------------------------------
sub enter_scope
	{
		my ($self,$node,$mode) = @_;

		return $self->_scope_add_functions($_[3]) if 'functions' eq $mode;
		return $self->_scope_add_symbols($_[3]) if 'symbols' eq $mode;

		my $ncount = 0;
		$ncount = ${$self->{_OB_DATA}->{scopes}}[0]->{count}
			if -1 != $#{$self->{_OB_DATA}->{scopes}};

		my $creator = (ref $node ? sprintf('<%s> from line %d', $node->op, 1+$node->line) : '<unknown>');

		do{local($self->{verbose_indent})={0x20=>-1};$self->verbose(0x20,'>>')},
		$self->verbose(0x20, "Entering scope #%d (%s)\n", 1+@{$self->{_OB_DATA}->{scopes}}, $creator);
		$self->verbose_indent(0x20,1);

		my $options = {};

		if (-1 != $#{$self->{_OB_DATA}->{scopes}})
			{
				$options = {%{$self->{_OB_DATA}->{scopes}->[0]->{options}}};
			}

		unshift @{$self->{_OB_DATA}->{scopes}}, {
			node_depth => 0,
			options => $options,
			creator => $creator,
			mode => $mode,
			functions => undef,
			symbols => undef,
			parameter_vars => {},
			ids => {},
			count => $ncount,
			ignore => [0]
		};
	}
##------------------------------------------------------------------------------
sub leave_scope
	{
		my ($self,$n) = ($_[0],$_[1]||1);

		$self->verbose_indent(0x20,-1),
		do{local($self->{verbose_indent})={0x20=>-1};$self->verbose(0x20,'<<')},
		$self->verbose(0x20, "Left scope #%d (%s)\n", 0+@{$self->{_OB_DATA}->{scopes}}, $self->{_OB_DATA}->{scopes}->[0]->{creator}),
		shift @{$self->{_OB_DATA}->{scopes}} while $n--;
		do {local($self->{verbose_indent});$self->verbose(0x20, "\n%s\n\n", ('~'x80))}
	}
##------------------------------------------------------------------------------
sub _scope_add_functions
	{
		my $self = shift;
		my $names = shift or return;

		$self->verbose(0x20, "Mapping scope #%d's functions...\n", $self->scope_level);
		$self->verbose(0x20, "\t[scope nesting too low]\n"), return
			if defined($self->option(qq{map_scope_start})) && $self->option(qq{map_scope_start}) > $self->scope_level;

		MAP_FUNC: foreach my $func (keys %$names)
			{
				next MAP_FUNC unless defined($self->{filter_instance}->{q{Yasco::Filter::MapId}});

				my $f = $self->{filter_instance}->{q{Yasco::Filter::MapId}};
				$self->verbose(0x20, "\t[*] `%s` is ignored by ignore definition\n", $func),
				next MAP_FUNC if 1 == ($f->{IGNORE}->{$func} || 0);

				my $newname = $f->newname($self,$func);

				${$self->{_OB_DATA}->{scopes}}[0]->{ids}->{$func} = $newname;
				$names->{$func} = $newname;

				$self->verbose(0x20, "\t[*] `%s` is '%s'\n", $func, $newname);
			}

		${$self->{_OB_DATA}->{scopes}}[0]->{functions} = $names;
	}
##------------------------------------------------------------------------------
sub _scope_add_symbols
	{
		my $self = shift;
		my $symbols = shift or return;

		$self->verbose(0x20, "Mapping scope #%d's symbols...\n", $self->scope_level);
		$self->verbose(0x20, "\t[scope nesting too low]\n"), return
			if defined($self->option(qq{map_scope_start})) && $self->option(qq{map_scope_start}) > $self->scope_level;

		MAP_FUNC: foreach my $var (keys %$symbols)
			{
				my $newname = undef;

				next unless defined($self->{filter_instance}->{q{Yasco::Filter::MapId}});

				my $f = $self->{filter_instance}->{q{Yasco::Filter::MapId}};
				$self->verbose(0x20, "\t[*] `%s` is ignored by ignore definition\n", $var),
				next MAP_FUNC if 1 == ($f->{IGNORE}->{$var} || 0);

				for (my $idx = 0; $idx < 0+@{$self->{_OB_DATA}->{scopes}}; $idx++)
					{
						unless (defined(($self->{_OB_DATA}->{scopes}->[$idx]->{parameter_vars} || {})->{$var}))
							{
								last if 'collect' eq $self->{_OB_DATA}->{scopes}->[$idx]->{mode} &&
										$self->{_OB_DATA}->{scopes}->[$idx]->{creator} =~ m{^<FUNC_DECL>};
								next;
							}

						warn sprintf("[WARNING] Scope #%d (%s) redeclares a local variable that's a function parameter: \"%s\" (declared \@%s)\n",
							$self->scope_level, $self->{_OB_DATA}->{scopes}->[0]->{creator},
							$var, $self->{_OB_DATA}->{scopes}->[$idx]->{creator});
						$f->{RENAMED}->{$var} = $self->{_OB_DATA}->{scopes}->[$idx]->{parameter_vars}->{$var};
						last;
					}

				$newname = $f->{RENAMED}->{$var}		if defined($f->{RENAMED}->{$var});
				$newname = $f->newname($self,$var)	unless defined($newname);

				${$self->{_OB_DATA}->{scopes}}[0]->{ids}->{$var} = $newname;
				$symbols->{$var} = $newname;

				$self->verbose(0x20, "\t[*] `%s` is '%s'\n", $var, $newname);
			}

		${$self->{_OB_DATA}->{scopes}}[0]->{symbols} = $symbols;
	}
##------------------------------------------------------------------------------
sub scope_level
	{
		my $self = shift;
		0+@{$self->{_OB_DATA}->{scopes}};
	}
##------------------------------------------------------------------------------
sub _obfuscate
	{
		my $self = shift;
		my $code;
		my $pp = {};

		GET_CODE: {
			$Yasco::Node::format = 1,
			$code = $self->first_node->reconstruct($self),
			last GET_CODE
				if $self->option(qq{deobfuscate});

			$code = $self->first_node->reconstruct unless $self->option(qq{no_obfuscate});
			$code = $self->p->{DATA} if $self->option(qq{no_obfuscate});
		}

		$code = join("\n", @Yasco::prefix_code) . $code;

		foreach my $fid (sort keys %{$self->{filters}})
			{
				foreach my $filter (@{$self->{filters}->{$fid}})
					{
						$fid = ref $filter;
						next if defined($pp->{$fid});
						$pp->{$fid} = 1;
						$filter->post_process($self, \$code);
					}
			}

		$code #. Dumper($self->first_node);
	}
##------------------------------------------------------------------------------
sub insert_eval
	{
		my $self = shift;
		return if defined($Yasco::prefixed_codes->{'eval'});
		$Yasco::prefixed_codes->{'eval'} = 'eval';# . int(99*rand);
		push @Yasco::prefix_code, 'var _' . $Yasco::prefixed_codes->{'eval'} . '=function(a0){return eval(a0);};';
		1
	}
##------------------------------------------------------------------------------
1